package ru.amse.tsyganov.jumleditor.main;

import java.io.File;
import java.io.IOException;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;

import ru.amse.tsyganov.jumleditor.commands.ViewsManager;
import ru.amse.tsyganov.jumleditor.graphicaleditor.GraphicalEditor;
import ru.amse.tsyganov.jumleditor.graphicaleditor.ItemsSelectionListener;
import ru.amse.tsyganov.jumleditor.model.ActivityGraph;
import ru.amse.tsyganov.jumleditor.model.State;
import ru.amse.tsyganov.jumleditor.model.Transition;
import ru.amse.tsyganov.jumleditor.properties.PropertiesTable;
import ru.amse.tsyganov.jumleditor.storing.Storage;
import ru.amse.tsyganov.jumleditor.storing.StoreException;
import ru.amse.tsyganov.jumleditor.validator.Problem;
import ru.amse.tsyganov.jumleditor.validator.ProblemSelectionListener;
import ru.amse.tsyganov.jumleditor.validator.ProblemsList;
import ru.amse.tsyganov.jumleditor.validator.ProblemsTable;
import ru.amse.tsyganov.jumleditor.validator.Validator;
import ru.amse.tsyganov.jumleditor.view.ModelChangeListener;
import ru.amse.tsyganov.jumleditor.view.SelectedItems;
import ru.amse.tsyganov.jumleditor.view.preferences.Images;
import ru.amse.tsyganov.jumleditor.view.representation.ActivityGraphView;
import ru.amse.tsyganov.jumleditor.view.representation.StateView;
import ru.amse.tsyganov.jumleditor.view.representation.TransitionView;

public class Editor {
	
	class ActivityGraphChangeListener implements ModelChangeListener {
		
		private final ActivityGraphView<? extends ActivityGraph> activityGraphView;
		
		public ActivityGraphChangeListener(
				ActivityGraphView<? extends ActivityGraph> activityGraphView) {
			
			this.activityGraphView = activityGraphView;
		}
		
		public void modelChanged(ActivityGraph activityGraph) {
			ProblemsList probems = Validator.validate(activityGraphView);
			probems.addProblemSelectionListener(problemSelectionListener);
			problemsTable.setSource(probems);
		}
		
	}
	
	private static final String SHELL_CAPTION = "JUmlEditor";
		
	private static final int INITIAL_SHELL_WIDTH = 700;
	
	private static final int INITIAL_SHELL_HEIGHT = 700;
	
	private final Display display;
	
	private final Shell sShell;
	
	private final GraphicalEditor graphicalEditor;
	
	private final ViewsManager viewsManager;
	
	private final TabFolder tabFolder; 
	
	private final PropertiesTable propertiesTable;
	
	private final ProblemsTable problemsTable;
	
	private final SashForm composite;
	
	private final Menu menu;
	
	private final ItemsSelectionListener itemsSelectionListener = 
		new ItemsSelectionListener() {

			public void selectedItemsChanged(SelectedItems selectedItems) {
				
				problemsTable.getTable().deselectAll();
				
				if (selectedItems.selectedStatesSize() + 
						selectedItems.selectedTransitionsSize() == 1) {
									
					if (selectedItems.selectedStatesSize() == 1) {
						StateView<? extends State> selectedState = 
							selectedItems.getSelectedStates().
								iterator().next();
						
						propertiesTable.setSource(selectedState);
						
						int i = 0;
						for (Problem p : problemsTable.getProblemsList().getList()) {
							if (p.getProblemPlace().selectedStatesContains(selectedState)) {
								problemsTable.getTable().select(i);
							}
							i++;
						}	
					}
					
					if (selectedItems.selectedTransitionsSize() == 1) {
						TransitionView<? extends Transition> selectedTransition =
							selectedItems.getSelectedTransitions().
								iterator().next();
						
						propertiesTable.setSource(selectedTransition);
						
						int i = 0;
						for (Problem p : problemsTable.getProblemsList().getList()) {
							if (p.getProblemPlace().selectedTransitionsContains(selectedTransition)) {
								problemsTable.getTable().select(i);
							}
							i++;
						}
					}
					
					problemsTable.getTable().redraw();
				} else {
					propertiesTable.clear();
				}
			}
		
		};
		
	private final ProblemSelectionListener problemSelectionListener = 
		new ProblemSelectionListener() {

		public void problemSelected(Problem p) {
			graphicalEditor.setSelectedItems(p.getProblemPlace());
		}
		
	};
		
	private String fileName = null;
	
	public Editor(ActivityGraphView<ActivityGraph> activityGraphView) {
				
		display = new Display();
		
		sShell = new Shell(display);
		
		final int VIEWS_MANAGER_SIZE = 20; 
		viewsManager = new ViewsManager(VIEWS_MANAGER_SIZE);
		
		composite = new SashForm(sShell, SWT.VERTICAL);
				
		graphicalEditor = new GraphicalEditor(composite, activityGraphView, viewsManager);
		
		tabFolder = new TabFolder(composite, SWT.NONE);
		
		composite.setWeights(new int[] {3, 1});
		
		propertiesTable = new PropertiesTable(tabFolder, 
				INITIAL_SHELL_WIDTH - 20, viewsManager);
		
		problemsTable = new ProblemsTable(tabFolder, INITIAL_SHELL_WIDTH - 20);
		
		menu = new Menu(sShell, SWT.BAR);
		
		sShell.setMenuBar(menu);
		
		initMenu();
		
		createSShell();
				
		bindActivityGraphView(activityGraphView);
		
		openAndWaitEvents();
	}

	private void bindActivityGraphView(ActivityGraphView<ActivityGraph> activityGraphView) {
		
		activityGraphView.getModel().addModelChangeListener(
				new ActivityGraphChangeListener(activityGraphView));
		
		ProblemsList probems = Validator.validate(activityGraphView);
		probems.addProblemSelectionListener(problemSelectionListener);
		problemsTable.setSource(probems);
		
		graphicalEditor.addSelectionListener(itemsSelectionListener);
	}

	private void openAndWaitEvents() {
		sShell.open();
		
		while(!sShell.isDisposed()) {
		   if(!display.readAndDispatch()) {
		       display.sleep();
		   }
		}
		display.dispose();
	}

	private void createSShell() {
		
		sShell.setText(SHELL_CAPTION);
		sShell.setImage(Images.getShellImage());
		
		initPropertiesTable();
		initProblemsTable();
		
		sShell.setLayout(new FillLayout());
				
		sShell.setSize(new Point(INITIAL_SHELL_WIDTH, 
				INITIAL_SHELL_HEIGHT));
		
		assert sShell != null;
	}

	private void initPropertiesTable() {
		assert sShell != null;
		assert propertiesTable != null;

		TabItem tabItem = new TabItem(tabFolder, SWT.NONE);
		tabItem.setControl(propertiesTable.getTable());
		tabItem.setText("Properties");	
	}
	
	private void initProblemsTable() {
		assert sShell != null;
		assert problemsTable != null;
		
		TabItem tabItem = new TabItem(tabFolder, SWT.NONE);
		tabItem.setControl(problemsTable.getTable());
		tabItem.setText("Problems");
	}
	
	private void initMenu() {
		MenuItem fileMenuItem = new MenuItem(menu, SWT.CASCADE);
		fileMenuItem.setText("&File");
		
		Menu fileMenu = new Menu(sShell, SWT.DROP_DOWN);
		fileMenuItem.setMenu(fileMenu);
		
		MenuItem newMenuItem = new MenuItem(fileMenu, SWT.PUSH);
		newMenuItem.setText("&New\tCtrl+N");
		newMenuItem.setAccelerator(SWT.CTRL + 'N');
		
		MenuItem openMenuItem = new MenuItem(fileMenu, SWT.PUSH);
		openMenuItem.setText("&Open\tCtrl+O");
		openMenuItem.setAccelerator(SWT.CTRL + 'O');
		
		new MenuItem(fileMenu, SWT.SEPARATOR);
		
		MenuItem saveMenuItem = new MenuItem(fileMenu, SWT.PUSH);
		saveMenuItem.setText("&Save\tCtrl+S");
		saveMenuItem.setAccelerator(SWT.CTRL + 'S');
		
		MenuItem saveAsMenuItem = new MenuItem(fileMenu, SWT.PUSH);
		saveAsMenuItem.setText("Save &As...");
		
		new MenuItem(fileMenu, SWT.SEPARATOR);
		
		MenuItem exitMenuItem = new MenuItem(fileMenu, SWT.PUSH);
		exitMenuItem.setText("E&xit\tCtrl+X");
		exitMenuItem.setAccelerator(SWT.CTRL + 'X');
		
		exitMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				sShell.close();
			}
			
		});
		
		openMenuItem.addSelectionListener(new SelectionListener() {
			
			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				try {
					FileDialog fileDialog = new FileDialog(sShell, SWT.OPEN);
					fileDialog.setFilterExtensions(new String[] {"*.xml"});
					String newFileName = fileDialog.open();
					
					if (newFileName == null) {
						return;
					}
					
					fileName = newFileName;
					graphicalEditor.getActivityGraphView().clear();
					Storage.read(newFileName, graphicalEditor.getActivityGraphView());
					sShell.setText(SHELL_CAPTION + " - " + fileName);
					graphicalEditor.getCanvas().redraw();
				} catch (IOException e1) {
					showErrorMessage(e1);
				} catch (StoreException e1) {
					showErrorMessage(e1);
				}
			}
		});
		
		saveAsMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				FileDialog fileDialog = new FileDialog(sShell, SWT.SAVE);
				fileDialog.setFilterExtensions(new String[] {"*.xml"});
				String newFileName = fileDialog.open();
				askAndSave(newFileName);				
			}
			
		});
		
		saveMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				if (fileName == null) {
					FileDialog fileDialog = new FileDialog(sShell, SWT.SAVE);
					fileDialog.setFilterExtensions(new String[] {"*.xml"});
					String newFileName = fileDialog.open();
					askAndSave(newFileName);
				} else {
					saveFile();
				}
			}
			
		});
		
		newMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				fileName = null;
				
				graphicalEditor.getActivityGraphView().clear();
				graphicalEditor.getCanvas().redraw();
			}
			
		});
		
		MenuItem editMenuItem = new MenuItem(menu, SWT.CASCADE);
		editMenuItem.setText("&Edit");
		
		Menu editMenu = new Menu(sShell, SWT.DROP_DOWN);
		editMenuItem.setMenu(editMenu);
		
		MenuItem deleteMenuItem = new MenuItem(editMenu, SWT.PUSH);
		deleteMenuItem.setText("&Delete\tDelete");
		deleteMenuItem.setAccelerator(SWT.DEL);
		
		new MenuItem(editMenu, SWT.SEPARATOR);
		
		MenuItem undoMenuItem = new MenuItem(editMenu, SWT.PUSH);
		undoMenuItem.setText("&Undo\tCtrl+Z");
		undoMenuItem.setAccelerator(SWT.CTRL + 'Z');
		
		MenuItem redoMenuItem = new MenuItem(editMenu, SWT.PUSH);
		redoMenuItem.setText("&Redo\tCtrl+Y");
		redoMenuItem.setAccelerator(SWT.CTRL + 'Y');
		
		deleteMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				graphicalEditor.deleteSelectedItems();
			}
			
		});
		
		undoMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				graphicalEditor.undo();
			}
			
		});
		
		redoMenuItem.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
				widgetSelected(e);
			}

			public void widgetSelected(SelectionEvent e) {
				graphicalEditor.redo();
			}
			
		});
	}
	
	private void askAndSave(String newFileName) {
		
		if (newFileName == null) {
			return;
		} 
		
		if (new File(newFileName).exists()) {
			if (MessageDialog.openQuestion(sShell, "JUmlEditor", 
				"File already exists. Replace?")) {
		
				fileName = newFileName;
				saveFile();
				sShell.setText(SHELL_CAPTION + " - " + fileName);
			}
		} else {
			saveFile();
			sShell.setText(SHELL_CAPTION + " - " + fileName);
		}
	}
	
	private void showErrorMessage(final Exception e) {
		new ErrorMessage(sShell, e).open();
	}
	
	private boolean saveFile() {
		assert fileName != null;
		try {
			Storage.write(graphicalEditor.getActivityGraphView(), fileName);
			return true;
		} catch (IOException e) {
			showErrorMessage(e);
		} catch (StoreException e) {
			showErrorMessage(e);
		}
		return false;
	}

}
